Skip to content

feat(analyzers): code-fix for TUnit0049 to insert [MatrixDataSource] (#5613 C)#5620

Merged
thomhurst merged 3 commits intomainfrom
fix/5613-matrix-implicit-datasource
Apr 18, 2026
Merged

feat(analyzers): code-fix for TUnit0049 to insert [MatrixDataSource] (#5613 C)#5620
thomhurst merged 3 commits intomainfrom
fix/5613-matrix-implicit-datasource

Conversation

@thomhurst
Copy link
Copy Markdown
Owner

Summary

Part C of #5613. Adds a Roslyn code-fix for TUnit0049 (MatrixDataSourceAttributeRequired) so the error is auto-repairable.

When a method has [Matrix] parameters (or a class has [Matrix] constructor parameters) but is missing [MatrixDataSource], TUnit0049 fires as an error. Previously the user had to hand-edit. Now the IDE offers Add [MatrixDataSource] which inserts the attribute on the diagnosed method or type.

Note on scope

An earlier exploration tried making [MatrixDataSource] implicit whenever [Matrix] params were present, but that touched source-gen + reflection engine + analyzer deletion and risked changing established semantics. A code-fix is a smaller, more discoverable change that leaves the explicit-attribute model intact.

Test plan

  • Adds_MatrixDataSource_On_Method — method gains [MatrixDataSource]
  • Adds_MatrixDataSource_On_Class — class gains [MatrixDataSource]
  • Manual: trigger TUnit0049 in an IDE and apply the code-fix

…ce] (#5613 C)

Registers a code-fix on TUnit0049 (MatrixDataSourceAttributeRequired)
that inserts [MatrixDataSource] on the diagnosed method or class, so
users hitting the error can apply the fix directly rather than hand-
editing.
Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: MatrixDataSourceCodeFixProvider

Overall this is clean, well-scoped work that fits neatly into the existing code-fix pattern. The two-test approach (method + class) covers both diagnostic sites. A few observations:


Minor: SyntaxFactory.ParseName vs SyntaxFactory.IdentifierName

// Current
SyntaxFactory.ParseName("MatrixDataSource")

// More idiomatic for an unqualified name
SyntaxFactory.IdentifierName("MatrixDataSource")

ParseName triggers a full parse pass and is intended for potentially-qualified names (e.g., "TUnit.Core.MatrixDataSource"). For a simple identifier, IdentifierName is the canonical choice and is used throughout Roslyn's own code-fix providers. Not a correctness issue, but prefer the precise API.


Minor: Consistency — const string Title pattern

All existing fixers (e.g., VirtualHookOverrideCodeFixProvider) extract the title into a const so it's shared between title: and equivalenceKey::

private const string Title = "Add [MatrixDataSource]";

// then:
CodeAction.Create(title: Title, ..., equivalenceKey: Title)

The PR uses two separate string literals. If the title is ever renamed, it's easy to update one and forget the other (which breaks fix-all equivalence grouping). Extracting to a const keeps them in sync automatically.


Minor: Redundant using TUnit.Analyzers; in peer file

VirtualHookOverrideCodeFixProvider.cs explicitly imports using TUnit.Analyzers; for Rules, while this file omits it and relies on enclosing-namespace resolution (TUnit.Analyzers.CodeFixers is a child of TUnit.Analyzers). It compiles either way, but adding the explicit import would be consistent with the neighbouring fixer.


Not an issue: Missing using TUnit.Core;

The concern "what if the file doesn't have using TUnit.Core;?" doesn't apply here — the diagnostic only fires when [Matrix] is already present, and [Matrix] itself requires TUnit.Core, so the using is guaranteed to exist.


Not an issue: DocumentEditor vs document.WithSyntaxRoot

The choice of DocumentEditor (vs the raw WithSyntaxRoot approach in VirtualHookOverrideCodeFixProvider) is fine — DocumentEditor is actually the recommended pattern for additive changes and integrates better with the workspace's formatting pipeline.


Summary: The core logic is correct and the test coverage is solid. The three minor points above are all about consistency with existing conventions rather than correctness. Happy to approve once/if any of those are addressed, or as-is if the author prefers to leave them.

@codacy-production
Copy link
Copy Markdown

codacy-production Bot commented Apr 18, 2026

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 4 complexity

Metric Results
Complexity 4

View in Codacy

TIP This summary will be updated as you push new changes. Give us feedback

…trixDataSource fix

Addresses review feedback on #5620:
- Replace SyntaxFactory.ParseName with SyntaxFactory.IdentifierName
  for the unqualified "MatrixDataSource" — ParseName invokes the full
  parser where IdentifierName constructs directly.
- Extract the action title to a const so the code-action title and
  equivalenceKey stay in sync if either is renamed.
Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up Review

The previous feedback has been addressed:

  • ✅ used (not )
  • ✅ extracted and used for both and

The one remaining minor point from last round — adding an explicit using TUnit.Analyzers; for consistency with VirtualHookOverrideCodeFixProvider — is cosmetic and compiles fine either way. Not a blocker.

One new observation: fallback is unreachable

SyntaxNode updated = target switch
{
    MethodDeclarationSyntax method => method.AddAttributeLists(attributeList),
    TypeDeclarationSyntax type    => type.AddAttributeLists(attributeList),
    _ => target,   // dead code
};

The _ branch makes editor.ReplaceNode(target, target) a no-op. TUnit0049 can only be diagnosed on a method or a type, so this branch is never reached. Consider replacing it with _ => throw new InvalidOperationException($"Unexpected node kind: {target.Kind()}")` — this turns a silent no-op (wrong fix silently produces unchanged code) into a loud invariant violation that surfaces during development/testing if the analyzer ever fires somewhere unexpected.

Overall

The implementation is correct, well-tested, and consistent with the existing code-fix patterns. The two-test suite covers both diagnostic sites cleanly. Ready to merge.

Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up Review

The previous feedback has been addressed:

  • SyntaxFactory.IdentifierName used (not ParseName)
  • private const string Title extracted and used for both title: and equivalenceKey:

The one remaining minor point from last round (explicit using TUnit.Analyzers; for consistency) is cosmetic and compiles fine. Not a blocker.

New observation: _ => target fallback is a silent no-op

The _ arm in the switch produces editor.ReplaceNode(target, target) — replacing a node with itself. Since TUnit0049 only fires on methods and types, this branch is unreachable. The concern is that a silent no-op is worse than a loud failure: if the analyzer ever fires somewhere unexpected, the fix would appear to succeed but produce no visible change.

Consider replacing it with an exception:

_ => throw new InvalidOperationException($"Unexpected node kind: {target.Kind()}")

This turns a hidden invariant violation into an immediate, diagnosable error during development or testing, rather than a silently-wrong fix.

Overall

The implementation is correct and the two tests cover both diagnostic sites cleanly. Previous review feedback has been fully addressed. Ready to merge once the unreachable branch is handled (or intentionally kept as a safe fallback — either is defensible).

…e fix

The ancestor filter guarantees target is MethodDeclarationSyntax or
TypeDeclarationSyntax, so the default arm was unreachable and silently
replaced the node with itself. Fail loudly if the invariant ever breaks.
Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up Review — All Feedback Addressed

All three points from previous rounds have been resolved in the current diff:

  • SyntaxFactory.IdentifierName used (not ParseName)
  • private const string Title extracted and shared between title: and equivalenceKey:
  • _ => throw new InvalidOperationException(...) replaces the silent no-op fallback

The cosmetic point (explicit using TUnit.Analyzers;) is the only thing untouched, and it's not a blocker — the code compiles and behaves correctly either way.

Overall

The implementation is clean, correct, and consistent with the existing code-fix patterns. Both diagnostic sites (method and class) are covered by tests. Ready to merge.

@thomhurst thomhurst enabled auto-merge (squash) April 18, 2026 10:44
@thomhurst thomhurst merged commit eb8ef98 into main Apr 18, 2026
17 of 18 checks passed
@thomhurst thomhurst deleted the fix/5613-matrix-implicit-datasource branch April 18, 2026 11:53
intellitect-bot pushed a commit to IntelliTect/EssentialCSharp.Web that referenced this pull request Apr 21, 2026
Updated [TUnit](https://github.com/thomhurst/TUnit) from 1.35.2 to
1.37.10.

<details>
<summary>Release notes</summary>

_Sourced from [TUnit's
releases](https://github.com/thomhurst/TUnit/releases)._

## 1.37.10

<!-- Release notes generated using configuration in .github/release.yml
at v1.37.10 -->

## What's Changed
### Other Changes
* docs(test-filters): add migration callout for --filter →
--treenode-filter by @​johnkattenhorn in
thomhurst/TUnit#5628
* fix: re-enable RPC tests and modernize harness (#​5540) by @​thomhurst
in thomhurst/TUnit#5632
* fix(mocks): propagate [Obsolete] and null-forgiving raise dispatch
(#​5626) by @​JohnVerheij in
thomhurst/TUnit#5631
* ci: use setup-dotnet built-in NuGet cache by @​thomhurst in
thomhurst/TUnit#5635
* feat(playwright): propagate W3C trace context into browser contexts by
@​thomhurst in thomhurst/TUnit#5636
### Dependencies
* chore(deps): update tunit to 1.37.0 by @​thomhurst in
thomhurst/TUnit#5625

## New Contributors
* @​johnkattenhorn made their first contribution in
thomhurst/TUnit#5628
* @​JohnVerheij made their first contribution in
thomhurst/TUnit#5631

**Full Changelog**:
thomhurst/TUnit@v1.37.0...v1.37.10

## 1.37.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.37.0 -->

## What's Changed
### Other Changes
* fix: stabilize flaky tests across analyzer, OTel, and engine suites by
@​thomhurst in thomhurst/TUnit#5609
* perf: engine hot-path allocation wins (#​5528 B) by @​thomhurst in
thomhurst/TUnit#5610
* feat(analyzers): detect collection IsEqualTo reference equality
(TUnitAssertions0016) by @​thomhurst in
thomhurst/TUnit#5615
* perf: consolidate test dedup + hook register guards (#​5528 A) by
@​thomhurst in thomhurst/TUnit#5612
* perf: engine discovery/init path cleanup (#​5528 C) by @​thomhurst in
thomhurst/TUnit#5611
* fix(assertions): render collection contents in IsEqualTo failure
messages (#​5613 B) by @​thomhurst in
thomhurst/TUnit#5619
* feat(analyzers): code-fix for TUnit0015 to insert CancellationToken
(#​5613 D) by @​thomhurst in
thomhurst/TUnit#5621
* fix(assertions): add Task reference forwarders on
AsyncDelegateAssertion by @​thomhurst in
thomhurst/TUnit#5618
* test(asp-net): fix race in FactoryMethodOrderTests by @​thomhurst in
thomhurst/TUnit#5623
* feat(analyzers): code-fix for TUnit0049 to insert [MatrixDataSource]
(#​5613 C) by @​thomhurst in
thomhurst/TUnit#5620
* fix(pipeline): isolate AOT publish outputs to stop clobbering pack
DLLs (#​5622) by @​thomhurst in
thomhurst/TUnit#5624
### Dependencies
* chore(deps): update tunit to 1.36.0 by @​thomhurst in
thomhurst/TUnit#5608
* chore(deps): update modularpipelines to 3.2.8 by @​thomhurst in
thomhurst/TUnit#5614


**Full Changelog**:
thomhurst/TUnit@v1.36.0...v1.37.0

## 1.36.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.36.0 -->

## What's Changed
### Other Changes
* fix: don't render test's own trace as Linked Trace in HTML report by
@​thomhurst in thomhurst/TUnit#5580
* fix(docs): benchmark index links 404 by @​thomhurst in
thomhurst/TUnit#5587
* docs: replace repeated benchmark link suffix with per-test
descriptions by @​thomhurst in
thomhurst/TUnit#5588
* docs: clearer distributed tracing setup and troubleshooting by
@​thomhurst in thomhurst/TUnit#5597
* fix: auto-suppress ExecutionContext flow for hosted services (#​5589)
by @​thomhurst in thomhurst/TUnit#5598
* feat: auto-align DistributedContextPropagator to W3C by @​thomhurst in
thomhurst/TUnit#5599
* feat: TUnit0064 analyzer + code fix for direct WebApplicationFactory
inheritance by @​thomhurst in
thomhurst/TUnit#5601
* feat: auto-propagate test trace context through IHttpClientFactory by
@​thomhurst in thomhurst/TUnit#5603
* feat: TUnit.OpenTelemetry zero-config tracing package by @​thomhurst
in thomhurst/TUnit#5602
* fix: restore [Obsolete] members removed in v1.27 (#​5539) by
@​thomhurst in thomhurst/TUnit#5605
* feat: generalize OTLP receiver for use outside TUnit.Aspire by
@​thomhurst in thomhurst/TUnit#5606
* feat: auto-configure OpenTelemetry in TestWebApplicationFactory SUT by
@​thomhurst in thomhurst/TUnit#5607
### Dependencies
* chore(deps): update tunit to 1.35.2 by @​thomhurst in
thomhurst/TUnit#5581
* chore(deps): update dependency typescript to ~6.0.3 by @​thomhurst in
thomhurst/TUnit#5582
* chore(deps): update dependency coverlet.collector to v10 by
@​thomhurst in thomhurst/TUnit#5600


**Full Changelog**:
thomhurst/TUnit@v1.35.2...v1.36.0

Commits viewable in [compare
view](thomhurst/TUnit@v1.35.2...v1.37.10).
</details>

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=TUnit&package-manager=nuget&previous-version=1.35.2&new-version=1.37.10)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This was referenced Apr 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant